<?php

declare(strict_types=1);

namespace App\Service\Client;

use App\Amqp\DelayProducer;
use App\Amqp\Producer\NoticeWithdrawAuditProducer;
use App\Constants\DefaultValue;
use App\Constants\ErrorCode;
use App\Constants\RedisKey;
use App\Dao\AdminUserDao;
use App\Dao\CooperativeDao;
use App\Dao\CooperativeUserDao;
use App\Dao\NoticeAdminDao;
use App\Dao\NoticeCooperativeDao;
use App\Dao\TaskExecDao;
use App\Dao\UserDao;
use App\Dao\UserGoldLogDao;
use App\Dao\UserWithdrawDao;
use App\Exception\BusinessException;
use App\Model\Cooperative;
use App\Model\User;
use App\Model\UserInfo;
use App\Model\UserWithdraw;
use App\Utils\LevelRule;
use App\Utils\Lock;
use App\Utils\Log;
use App\Utils\Notice\AdminWithdrawAudit;
use App\Utils\Notice\CooperativeWithdrawAudit;
use App\Utils\Notice\Sender;
use App\Utils\Wechat\OpenPlatform\MiniProgram;
use Hyperf\Amqp\Result;
use Hyperf\DbConnection\Db;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Redis\RedisFactory;
use App\Constants\Model\UserWithdraw as UserWithdrawConstants;
use App\Constants\Model\Cooperative as CooperativeConstants;
use App\Constants\Model\Notice as NoticeConstants;
use App\Constants\Model\TaskExec as TaskExecConstants;


class UserService extends BaseService
{
    /**
     * @var \Hyperf\Redis\RedisProxy|mixed|\Redis|null
     */
    protected $redis;

    /**
     * @Inject()
     * @var DelayProducer
     */
    protected $delayProducer;

    /**
     * @Inject()
     * @var UserDao
     */
    protected $userDao;

    /**
     * @Inject()
     * @var UserGoldLogDao
     */
    protected $userGoldLogDao;

    /**
     * @Inject()
     * @var UserWithdrawDao
     */
    protected $userWithdrawDao;


    /**
     * @Inject()
     * @var CooperativeDao
     */
    protected $cooperativeDao;

    /**
     * @Inject()
     * @var CooperativeUserDao
     */
    protected $cooperativeUserDao;

    /**
     * @Inject()
     * @var AdminUserDao
     */
    protected $adminUserDao;

    /**
     * @Inject()
     * @var NoticeCooperativeDao
     */
    protected $noticeCooperativeDao;

    /**
     * @Inject()
     * @var NoticeAdminDao
     */
    protected $noticeAdminDao;

    /**
     * @Inject()
     * @var TaskExecDao
     */
    protected $taskExecDao;


    public function __construct()
    {
        $this->redis = di(RedisFactory::class)->get('default');
    }


    /**
     * 个人信息
     * @return array[]
     */
    public function info(): array
    {
        $this->userInfo()->info;

        $data = $this->userInfo();

        //等级信息
        $data->levelInfo = [
            'commissionSum' => (int)$this->userGoldLogDao->getCommissionSum($this->userInfo()->id),//总金币数
            'minGold' => LevelRule::$rule[$this->userInfo()->level]['goldMin'],//当前等级最小金币
            'maxGold' => LevelRule::$rule[$this->userInfo()->level]['goldMax'],//当前等级最大金币
        ];

        $whereTask['user_id'] = $this->userInfo()->id;
        //未提交
        $taskExecNotAudit = $this->taskExecDao
            ->where($whereTask)
            ->where('status', TaskExecConstants::STATUS_NOT_AUDIT)
            ->count();
        //已提交
        $taskExecHasAudit = $this->taskExecDao
            ->where($whereTask)
            ->where('status', TaskExecConstants::STATUS_HAS_AUDIT)
            ->count();
        //仲裁
        $taskExecArbitration = $this->taskExecDao
            ->where($whereTask)
            ->where('status', TaskExecConstants::STATUS_ARBITRATION)
            ->count();
        $data->taskInfo = [
            'notAudit' => $taskExecNotAudit,
            'hasAudit' => $taskExecHasAudit,
            'arbitration' => $taskExecArbitration
        ];

        return [
            'user' => $data
        ];
    }


    /**
     * 个人信息-修改
     * @param array $data
     */
    public function infoUpdate(array $data): void
    {
        $dataUser = [];
        $dataUser['nickname'] = isset($data['nickname']) ? $data['nickname'] : '';
        $filePath = '';
        if (isset($data['avatar'])) {
            $dataUser['avatar'] = $data['avatar'];
        }

        $dataUserInfo = [];
        $dataUserInfo['sex'] = isset($data['sex']) ? $data['sex'] : 0;
        $dataUserInfo['qq'] = isset($data['qq']) ? $data['qq'] : '';
        $dataUserInfo['contact'] = isset($data['contact']) ? $data['contact'] : '';
        $dataUserInfo['description'] = isset($data['description']) ? $data['description'] : '';

        Db::beginTransaction();
        try {
            User::query()->where(['id' => $this->userInfo()->id])->update($dataUser);
            UserInfo::query()->updateOrInsert(['user_id' => $this->userInfo()->id], $dataUserInfo);
            Db::commit();
            if ($this->userInfo()->avatar != '') {
                $this->deleteFile($this->userInfo()->avatar);
            }
        } catch (\Throwable $e) {
            if ($filePath != '') {
                $this->deleteFile($filePath);
            }

            Db::rollBack();
            Log::get()->error($e->getMessage(), [$e->getMessage(), $data]);
            Log::get()->error($e->getTraceAsString());
            throw new BusinessException(ErrorCode::USER_INFO_UPDATE_ERROR);
        }
    }

    /**
     * 个人密码-修改
     * @param array $data
     */
    public function infoPasswordUpdate(array $data): void
    {
        $this->userInfo()->password = password_hash($data['password'], PASSWORD_BCRYPT);
        $this->userInfo()->save();
    }

    /**
     * 金币列表-佣金明细/平台奖励明细
     * @param array $data
     * @return array
     */
    public function goldList(array $data): array
    {
        $perPage = isset($data['perPage']) ? (int)$data['perPage'] : DefaultValue::PER_PAGE_DEFAULT;

        $page = $this->userGoldLogDao->paginate(
            ['user_id' => $this->userInfo()->id, 'type' => $data['type']],
            $perPage,
            ['*'],
            ['orderBy' => ['created_at', 'desc']]
        );

        return [
            'currentPage' => $page->currentPage(),//当前页
            'total' => $page->total(),//数据总数
            'hasMorePages' => $page->hasMorePages(),//是否还有更多页
            'data' => $page->items(),//数据
        ];
    }


    /**
     * 金币提现
     * @param array $data
     */
    public function withdraw(array $data): void
    {
        $lockKey = RedisKey::LOCK_MYSQL_TABLE_ID . 'user:' . $this->userInfo()->id;

        $Lock = new Lock();
        $lockState = $Lock->lock($lockKey, DefaultValue::LOCK_TTL);
        if (!$lockState) {
            throw new BusinessException(ErrorCode::LOCK_ERROR);
        }

        /** @var User $user */
        $user = $this->userDao->first(['id' => $this->userInfo()->id]);

        if ($data['type'] == UserWithdrawConstants::GOLD_TYPE_COMMISSION
            && $data['gold'] > $user->commission) {
            $Lock->unlock($lockKey);
            throw new BusinessException(ErrorCode::GOLD_NOT_ENOUGH);
        }

        if ($data['type'] == UserWithdrawConstants::GOLD_TYPE_AWARD
            && $data['gold'] > $user->award) {
            $Lock->unlock($lockKey);
            throw new BusinessException(ErrorCode::GOLD_NOT_ENOUGH);
        }

        Db::beginTransaction();
        try {
            if ($data['type'] == UserWithdrawConstants::GOLD_TYPE_COMMISSION) {
                $existingGold = $user->commission - $data['gold'];
                User::query()->where(['id' => $this->userInfo()->id])->update(['commission' => $existingGold]);
            } else {
                $existingGold = $user->award - $data['gold'];
                User::query()->where(['id' => $this->userInfo()->id])->update(['award' => $existingGold]);
            }

            /** @var Cooperative $cooperative */
            $cooperative = $this->cooperativeDao->first(['id' => $this->userInfo()->cooperative_id]);

            $userWithdraw = new UserWithdraw();
            $userWithdraw->user_id = $this->userInfo()->id;
            $userWithdraw->cooperative_id = $this->userInfo()->cooperative_id;
            $userWithdraw->gold = $data['gold'];
            $userWithdraw->status = UserWithdrawConstants::STATUS_PENDING;
            $userWithdraw->message = '';
            //通知发送平台
            $noticeSendPlatform = '';

            if ($cooperative->bind_wx_merchant == CooperativeConstants::BIND_WX_MERCHANT_YES) {
                $noticeSendPlatform = NoticeConstants::SEND_PLATFORM_COOPERATIVE;
                $userWithdraw->is_self_merchant = UserWithdrawConstants::IS_SELF_MERCHANT_YES;
            } elseif ($cooperative->bind_wx_merchant == CooperativeConstants::BIND_WX_MERCHANT_NO) {
                $noticeSendPlatform = NoticeConstants::SEND_PLATFORM_ADMIN;
                $userWithdraw->is_self_merchant = UserWithdrawConstants::IS_SELF_MERCHANT_NO;
            }
            $userWithdraw->gold_type = $data['type'];

            $userWithdraw->save();

            //提现通知
            $message = new NoticeWithdrawAuditProducer(
                [
                    'noticeSendPlatform' => $noticeSendPlatform,
                    'cooperative_id' => $this->userInfo()->cooperative_id
                ]
            );
            $this->delayProducer->produce($message, 1);

            Db::commit();
        } catch (\Throwable $e) {
            Db::rollBack();
            Log::get()->error($e->getCode(), [$e->getMessage(), $data]);
            Log::get()->error($e->getTraceAsString());
//            var_dump($e->getTraceAsString());
//            var_dump($e->getMessage());
            throw new BusinessException(ErrorCode::WITHDRAW_ERROR);
        } finally {
            $Lock->unlock($lockKey);
        }
    }


    /**
     * 提现列表
     * @param array $data
     * @return array
     */
    public function withdrawList(array $data): array
    {
        $perPage = isset($data['perPage']) ? (int)$data['perPage'] : DefaultValue::PER_PAGE_DEFAULT;

        $page = $this->userWithdrawDao->paginate(
            [
                'user_id' => $this->userInfo()->id,
                'gold_type' => $data['type']
            ],
            $perPage,
            ['*'],
            ['orderBy' => ['created_at', 'desc']]
        );
        return [
            'currentPage' => $page->currentPage(),//当前页
            'total' => $page->total(),//数据总数
            'hasMorePages' => $page->hasMorePages(),//是否还有更多页
            'data' => $page->items(),//数据
        ];
    }

    /**
     * 绑定支付宝
     * @param array $data
     */
    public function bindAlipay(array $data): void
    {
        $this->userInfo()->alipay = $data['alipay'];
        $this->userInfo()->save();
    }


    /**
     * 绑定微信
     * @param array $data
     */
    public function bindOpenid(array $data): void
    {
        try {
            $miniProgram = di(MiniProgram::class);
            $wxInfo = $miniProgram->login($this->cooperativeInfo()->wx_mp_appid, $data['code']);

            $this->userInfo()->wx_mp_openid = $wxInfo['openid'];
            $this->userInfo()->save();
        } catch (\Throwable $e) {
            Log::get()->error($e->getCode(), [$e->getMessage()]);
            Log::get()->error($e->getTraceAsString());
            throw new BusinessException(ErrorCode::BIND_OPENID_FAIL);
        }
    }


    /**
     * @param array $data
     * @return string
     */
    public function noticeWithdrawAudit(array $data): string
    {
        //判断两个小时内是否有发过且未读
        $beforeTime = time() - NoticeConstants::BEFORE_TIME_HAS_NOT_READ;
        $beforeDate = date('Y-m-d H:i:s', $beforeTime);

        if ($data['noticeSendPlatform'] == NoticeConstants::SEND_PLATFORM_COOPERATIVE) {
            $withdrawAuditCount = $this->userWithdrawDao
                ->where(
                    [
                        'cooperative_id' => $data['cooperative_id'],
                        'is_self_merchant' => UserWithdrawConstants::IS_SELF_MERCHANT_YES,
                        'status' => UserWithdrawConstants::STATUS_PENDING
                    ]
                )
                ->count();
            if ($withdrawAuditCount >= NoticeConstants::WITHDRAW_AUDIT_MAX) {
                $cooperativeUserIds = $this->cooperativeUserDao->getIds($data['cooperative_id']);

                $hasNotice = $this->noticeCooperativeDao
                    ->where(
                        [
                            'type' => NoticeConstants::COOPERATIVE_WITHDRAW_AUDIT,
                            'status' => NoticeConstants::STATUS_UNREAD,
                            'created_at_gt' => $beforeDate,
                            'cooperative_user_ids' => $cooperativeUserIds
                        ]
                    )
                    ->get();

                $hasNoticeIds = [];
                if ($hasNotice->count() > 0) {
                    $hasNoticeArray = $hasNotice->toArray();
                    $hasNoticeIds = array_column($hasNoticeArray, 'cooperative_user_id');
                }

                $needSendIds = array_diff($cooperativeUserIds, $hasNoticeIds);
                if (count($needSendIds) > 0) {
                    Sender::send(
                        $needSendIds,
                        new CooperativeWithdrawAudit()
                    );
                }
            }
        } elseif ($data['noticeSendPlatform'] == NoticeConstants::SEND_PLATFORM_ADMIN) {
            $withdrawAuditCount = $this->userWithdrawDao
                ->where(
                    [
                        'is_self_merchant' => UserWithdrawConstants::IS_SELF_MERCHANT_NO,
                        'status' => UserWithdrawConstants::STATUS_PENDING
                    ]
                )
                ->count();

            if ($withdrawAuditCount >= NoticeConstants::WITHDRAW_AUDIT_MAX) {
                $adminUserIds = $this->adminUserDao->getIds();

                $hasNotice = $this->noticeAdminDao
                    ->where(
                        [
                            'type' => NoticeConstants::ADMIN_WITHDRAW_AUDIT,
                            'status' => NoticeConstants::STATUS_UNREAD,
                            'created_at_gt' => $beforeDate,
                            'admin_user_ids' => $adminUserIds
                        ]
                    )
                    ->get();

                $hasNoticeIds = [];
                if ($hasNotice->count() > 0) {
                    $hasNoticeArray = $hasNotice->toArray();
                    $hasNoticeIds = array_column($hasNoticeArray, 'admin_user_id');
                }

                $needSendIds = array_diff($adminUserIds, $hasNoticeIds);
                if (count($needSendIds) > 0) {
                    Sender::send(
                        $needSendIds,
                        new AdminWithdrawAudit()
                    );
                }
            }
        }
        return Result::ACK;
    }
}